#include "mainwindow.h"
#include "ui_mainwindow.h"

#include <QMessageBox>
#include <QDateTime>
#include <QScreen>
#include <QValidator>

#define TIMEMS          qPrintable(QTime::currentTime().toString("HH:mm:ss zzz"))
#define TIME            qPrintable(QTime::currentTime().toString("HH:mm:ss"))
#define QDATE           qPrintable(QDate::currentDate().toString("yyyy-MM-dd"))
#define QTIME           qPrintable(QTime::currentTime().toString("HH-mm-ss"))
#define DATETIME        qPrintable(QDateTime::currentDateTime().toString("yyyy-MM-dd HH:mm:ss"))
#define STRDATETIME     qPrintable(QDateTime::currentDateTime().toString("yyyy-MM-dd-HH-mm-ss"))
#define STRDATETIMEMS   qPrintable(QDateTime::currentDateTime().toString("yyyy-MM-dd-HH-mm-ss-zzz"))
#define MyDATETIMEMS    qPrintable(QDateTime::currentDateTime().toString("MM-dd HH:mm:ss zzz"))

#define SENTYPETOTAL 51

#define SENSORATTRLEN (25)
#define CTRLATTRLEN (24)

static const char* gasChineseStr[SENTYPETOTAL] = {
    u8"甲烷",     u8"氢气",     u8"人工煤气", u8"液化气",     u8"油制气",   u8"丙烷",
    u8"己烷",     u8"环氧乙烷",  u8"硅烷",     u8"氯甲烷",     u8"乙炔",     u8"乙烯",
    u8"丙烯",     u8"苯乙烯",   u8"甲醇",     u8"乙醇",       u8"异丙醇",   u8"苯",
    u8"甲苯",     u8"二甲苯",   u8"丙酮",     u8"丁酮",       u8"甲醛",     u8"乙醚",
    u8"二甲醚",   u8"石油醚",   u8"乙酸乙酯",  u8"乙酸异戊酯",  u8"汽油",     u8"柴油",
    u8"可燃气体", u8"航空煤油", u8"四氢呋喃",  u8"丙烯腈",      	u8"DMF",      u8"氨气",
    u8"氧气",     u8"氯气",     u8"一氧化碳", u8"二氧化碳",    u8"一氧化氮", u8"二氧化氮",
    u8"二氧化硫", u8"硫化氢",   u8"氯化氢",   u8"氰化氢",     u8"磷化氢",    u8"氟化氢",
    u8"混合气", u8"其它",  u8"VOCs"};

//CRC高位字节值表
const quint8 auchCRCHi[] = {
    0x00, 0xc1, 0x81, 0x40, 0x01, 0xc0, 0x80, 0x41, 0x01, 0xc0,
    0x80, 0x41, 0x00, 0xc1, 0x81, 0x40, 0x01, 0xc0, 0x80, 0x41,
    0x00, 0xc1, 0x81, 0x40, 0x00, 0xc1, 0x81, 0x40, 0x01, 0xc0,
    0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40,
    0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1,
    0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41,
    0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1,
    0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,
    0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0,
    0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40,
    0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1,
    0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40,
    0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0,
    0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40,
    0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0,
    0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40,
    0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0,
    0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,
    0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0,
    0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,
    0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0,
    0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40,
    0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1,
    0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,
    0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0,
    0x80, 0x41, 0x00, 0xC1, 0x81, 0x40
};

//CRC低位字节值表
const quint8 auchCRCLo[] = {
    0x00, 0xC0, 0xC1, 0x01, 0xC3, 0x03, 0x02, 0xC2, 0xC6, 0x06,
    0x07, 0xC7, 0x05, 0xC5, 0xC4, 0x04, 0xCC, 0x0C, 0x0D, 0xCD,
    0x0F, 0xCF, 0xCE, 0x0E, 0x0A, 0xCA, 0xCB, 0x0B, 0xC9, 0x09,
    0x08, 0xC8, 0xD8, 0x18, 0x19, 0xD9, 0x1B, 0xDB, 0xDA, 0x1A,
    0x1E, 0xDE, 0xDF, 0x1F, 0xDD, 0x1D, 0x1C, 0xDC, 0x14, 0xD4,
    0xD5, 0x15, 0xD7, 0x17, 0x16, 0xD6, 0xD2, 0x12, 0x13, 0xD3,
    0x11, 0xD1, 0xD0, 0x10, 0xF0, 0x30, 0x31, 0xF1, 0x33, 0xF3,
    0xF2, 0x32, 0x36, 0xF6, 0xF7, 0x37, 0xF5, 0x35, 0x34, 0xF4,
    0x3C, 0xFC, 0xFD, 0x3D, 0xFF, 0x3F, 0x3E, 0xFE, 0xFA, 0x3A,
    0x3B, 0xFB, 0x39, 0xF9, 0xF8, 0x38, 0x28, 0xE8, 0xE9, 0x29,
    0xEB, 0x2B, 0x2A, 0xEA, 0xEE, 0x2E, 0x2F, 0xEF, 0x2D, 0xED,
    0xEC, 0x2C, 0xE4, 0x24, 0x25, 0xE5, 0x27, 0xE7, 0xE6, 0x26,
    0x22, 0xE2, 0xE3, 0x23, 0xE1, 0x21, 0x20, 0xE0, 0xA0, 0x60,
    0x61, 0xA1, 0x63, 0xA3, 0xA2, 0x62, 0x66, 0xA6, 0xA7, 0x67,
    0xA5, 0x65, 0x64, 0xA4, 0x6C, 0xAC, 0xAD, 0x6D, 0xAF, 0x6F,
    0x6E, 0xAE, 0xAA, 0x6A, 0x6B, 0xAB, 0x69, 0xA9, 0xA8, 0x68,
    0x78, 0xB8, 0xB9, 0x79, 0xBB, 0x7B, 0x7A, 0xBA, 0xBE, 0x7E,
    0x7F, 0xBF, 0x7D, 0xBD, 0xBC, 0x7C, 0xB4, 0x74, 0x75, 0xB5,
    0x77, 0xB7, 0xB6, 0x76, 0x72, 0xB2, 0xB3, 0x73, 0xB1, 0x71,
    0x70, 0xB0, 0x50, 0x90, 0x91, 0x51, 0x93, 0x53, 0x52, 0x92,
    0x96, 0x56, 0x57, 0x97, 0x55, 0x95, 0x94, 0x54, 0x9C, 0x5C,
    0x5D, 0x9D, 0x5F, 0x9F, 0x9E, 0x5E, 0x5A, 0x9A, 0x9B, 0x5B,
    0x99, 0x59, 0x58, 0x98, 0x88, 0x48, 0x49, 0x89, 0x4B, 0x8B,
    0x8A, 0x4A, 0x4E, 0x8E, 0x8F, 0x4F, 0x8D, 0x4D, 0x4C, 0x8C,
    0x44, 0x84, 0x85, 0x45, 0x87, 0x47, 0x46, 0x86, 0x82, 0x42,
    0x43, 0x83, 0x41, 0x81, 0x80, 0x40
};

const char* GetGasName(quint16 index)
{
    if(index >= SENTYPETOTAL) index = SENTYPETOTAL- 2;

    return gasChineseStr[index];
}
/*************************************************************************
    Function:       	GetDataCheck
    Description:	输入1组数据，然后计算该组数据的CRC16校验码
    Calls:          	// 被本函数调用的函数清单
    Called By:      	// 调用本函数的函数清单

    Input:
            *pData: 要进行校验的数据指针
            len: 要进行校验的数据长度
    Output:

    Return:
            CRC16校验码
*************************************************************************/
static quint16 GetCrc16(quint8 *pData, quint16 len)
{
    quint8 chHiCrc = 0xff;			//高CRC字节初始化
    quint8 chLowCrc = 0xff;		//低CRC字节初始化
    quint8 uIndex = 0;				//CRC循环中的索引
    quint8 HL;

    while(len--){//传输消息缓冲区
        uIndex = chHiCrc ^ *pData++;	//计算CRC
        HL = auchCRCHi[uIndex];
        chHiCrc = chLowCrc ^ HL;
        HL = auchCRCLo[uIndex];
        chLowCrc = HL;
    }

    return ((chHiCrc << 8) | chLowCrc);
}

MainWindow::MainWindow(QWidget *parent) :
    QMainWindow(parent),
    ui(new Ui::MainWindow),
    serialPortInfo(nullptr)
{
    ui->setupUi(this);

    QScreen *screen=QGuiApplication::primaryScreen();
    QRect mm=screen->availableGeometry() ;

    int h = mm.height(), w = mm.width();
    this->setFixedSize(w * 4 / 5, h * 4 / 5);

    ui->enbleTestBox->setEnabled(false);

    ui->overTimeEdit->setText(QString::number(overTick));
    ui->overTimeEdit->setValidator(new QIntValidator(500, 10000, this));

    ui->modbusPtlAddrEdit->setValidator(new QIntValidator(0, 65536, this));
    ui->modbusPtlLenEdit->setValidator(new QIntValidator(0, 128, this));

    ui->modbusCmdBox->addItem(u8"01:模块状态");
    ui->modbusCmdBox->addItem(u8"02:控制器状态");
    ui->modbusCmdBox->addItem(u8"03:探头&控制器属性");
    ui->modbusCmdBox->addItem(u8"04:探头浓度状态");

    ui->parityBox->addItem(u8"无校验");
    ui->parityBox->addItem(u8"奇校验");
    ui->parityBox->addItem(u8"偶校验");
    ui->parityBox->setCurrentIndex(1);

    ui->baudrateBox->addItem("4800");
    ui->baudrateBox->addItem("9600");
    ui->baudrateBox->addItem("19200");
    ui->baudrateBox->addItem("38400");
    ui->baudrateBox->addItem("57600");
    ui->baudrateBox->addItem("115200");
    ui->baudrateBox->setCurrentIndex(1);

    ui->opCmdBox->addItem(u8"1. 读取控制器属性");
    ui->opCmdBox->addItem(u8"2. 读取指定探头全属性");
    ui->opCmdBox->addItem(u8"3. 同步设备时间");
    ui->opCmdBox->addItem(u8"4. 读取设备容量");
    ui->opCmdBox->addItem(u8"5. 设置设备容量");
    ui->opCmdBox->addItem(u8"6. 复位设备");
    ui->opCmdBox->addItem(u8"7. 设备消声");

    ui->modbusSetTtextEdit->setFixedHeight(h / 20);

    int ww = w/20;
    ui->capcityEdit->setFixedWidth(ww);
    ui->modbusPtlAddrEdit->setFixedWidth(ww);
    ui->modbusPtlLenEdit->setFixedWidth(ww);

    for(int i = 1; i <= 255; i++)
    {
        QString tmp = QString("%1%2").arg(i, 3, 10, QChar('0')).arg(u8"号探头");
        ui->sensorAddrBox->addItem(tmp);
    }

    for(int i = 1; i <= 255; i++)
    {
        ui->clientAddrBox->addItem(QString::number(i));
    }

    serialPort = new QSerialPort(this);
    serialPort->setBaudRate(QSerialPort::Baud9600);
    serialPort->setDataBits(QSerialPort::Data8);
    serialPort->setParity(QSerialPort::OddParity);
    serialPort->setStopBits(QSerialPort::OneStop);
    serialPort->setFlowControl(QSerialPort::NoFlowControl);
    connect(serialPort, &QSerialPort::errorOccurred, this, &MainWindow::SlotPortError);
    connect(serialPort, &QSerialPort::readyRead, this, &MainWindow::SlotRead);

    timerWnd = new QTimer(this);
    if(timerWnd != nullptr) {
        connect(timerWnd, SIGNAL(timeout()), this, SLOT(SlotTimer()));
        timerWnd->setInterval(10);
        timerWnd->start();
    }
    refreshSerialPort();
}

MainWindow::~MainWindow()
{
    if(timerWnd != nullptr) {
        timerWnd->stop();
        disconnect(timerWnd, SIGNAL(timeout()), this, SLOT(SlotTimer()));
    }
    disconnect(serialPort, &QSerialPort::readyRead, this, &MainWindow::SlotRead);

    delete ui;
    closePort();
}

void MainWindow::on_serialBtn_clicked()
{
    if(isOpen) {
        if(isTiming) {
            on_enbleTestBox_clicked(false);
        }
        closePort();

        ui->serialBtn->setText(u8"打开串口");
        ui->serialPortBox->setEnabled(true);
        ui->refreshSerialBtn->setEnabled(true);
        ui->enbleTestBox->setEnabled(false);
        ui->baudrateBox->setEnabled(true);
        ui->parityBox->setEnabled(true);
    }
    else {
        closePort();

        QString port = ui->serialPortBox->currentText();
        serialPortInfo = new QSerialPortInfo(port);
        serialPort->setPort(*serialPortInfo);
        switch(ui->baudrateBox->currentIndex())
        {
        case 0: serialPort->setBaudRate(QSerialPort::Baud4800); break;
        case 1: serialPort->setBaudRate(QSerialPort::Baud9600); break;
        case 2: serialPort->setBaudRate(QSerialPort::Baud19200); break;
        case 3: serialPort->setBaudRate(QSerialPort::Baud38400); break;
        case 4: serialPort->setBaudRate(QSerialPort::Baud57600); break;
        case 5: serialPort->setBaudRate(QSerialPort::Baud115200); break;
        }
        switch(ui->parityBox->currentIndex())
        {
        case 0: serialPort->setParity(QSerialPort::NoParity); break;
        case 1: serialPort->setParity(QSerialPort::OddParity); break;
        case 2: serialPort->setParity(QSerialPort::EvenParity); break;
        }

        isOpen = serialPort->open(QIODevice::ReadWrite);
        if(!isOpen) {
            closePort();
        }
        else {
            ui->serialBtn->setText(u8"关闭串口");
            ui->serialPortBox->setEnabled(false);
            ui->refreshSerialBtn->setEnabled(false);
            ui->baudrateBox->setEnabled(false);
            ui->parityBox->setEnabled(false);
            ui->enbleTestBox->setEnabled(true);
        }
    }
}

void MainWindow::on_clearBtn_clicked()
{
    std::lock_guard<std::mutex> lock(mutexCalcNum);

    sendNum = 0;
    recvNum = 0;
    continuousNum = 0;
    conNum = 0;

    ui->frameRatioEdit->setText("0");
    ui->frameRatioEdit_2->setText("0");
    ui->sendNumEdit->setText("0");
    ui->recvNumEdit->setText("0");
    ui->logTextEdit->clear();
}

static const char* GetUnitName(quint16 type)
{
    static const char* unitName[5] = {"LEL","PPM","VOL","mg/m3","PPB"};

    if(type <= 4) return unitName[type];

    return u8"错误单位";
}

static const char* GetAlarmTypeName(quint16 type)
{
    static const char* alarmType[3] = {u8"上升报警",u8"两级报警",u8"下降报警"};

    if(type <= 2) return alarmType[type];

    return u8"错误类型";
}

void MainWindow::parseSensorAttrFrame(char *frameData)
{
    ui->logTextEdit->appendPlainText(u8"探头属性数据解包:");

    quint8 *data = reinterpret_cast<quint8 *>(frameData);

    QString tmp;
    quint32 phyaddr;
    quint16 tmp16, flen = 4;

    phyaddr = (data[0] << 24) + (data[1] << 16) + (data[2] << 8) + data[3];
    tmp = QString("%1:%2 | ").arg(u8"物理地址[0-1]:").arg(phyaddr);

    tmp16 = (data[flen] << 8) + data[flen+1];
    flen += 2;
    tmp += QString("%1:%2 | ").arg(u8"检测量程[2]:").arg(tmp16);

    tmp16 = data[flen++];
    tmp += QString("%1:%2 ").arg(u8"显示单位[3H]:").arg(GetUnitName(tmp16));
    tmp16 = data[flen++];
    tmp += QString("%1:%2").arg(u8"显示精度[3L]:").arg(tmp16);
    ui->logTextEdit->appendPlainText(tmp);

    tmp16 = data[flen++];
    tmp = QString("%1:%2 ").arg(u8"传感类型[4H]:").arg(tmp16);
    tmp16 = data[flen++];
    tmp += QString("%1:%2 | ").arg(u8"气体类型[4L]:").arg(GetGasName(tmp16));

    tmp16 = (data[flen] << 8) + data[flen+1];
    flen += 2;
    tmp += QString("%1:%2 | ").arg(u8"报警类型[5]:").arg(GetAlarmTypeName(tmp16));

    tmp16 = (data[flen] << 8) + data[flen+1];
    flen += 2;
    tmp += QString("%1:%2 | ").arg(u8"低限浓度[6]:").arg(tmp16);

    tmp16 = (data[flen] << 8) + data[flen+1];
    flen += 2;
    tmp += QString("%1:%2").arg(u8"高限浓度[7]:").arg(tmp16);
    ui->logTextEdit->appendPlainText(tmp);

    tmp16 = (data[flen] << 8) + data[flen+1];
    flen += 2;
    tmp = QString("%1:%2 | ").arg(u8"当前零点[8]:").arg(tmp16);

    tmp16 = (data[flen] << 8) + data[flen+1];
    flen += 2;
    tmp += QString("%1:%2 | ").arg(u8" 标1浓度[9]:").arg(tmp16);

    tmp16 = (data[flen] << 8) + data[flen+1];
    flen += 2;
    tmp += QString("%1:%2 | ").arg(u8"标1电压差[10]:").arg(tmp16);

    tmp16 = (data[flen] << 8) + data[flen+1];
    flen += 2;
    tmp += QString("%1:%2 | ").arg(u8"标2浓度[11]:").arg(tmp16);

    tmp16 = (data[flen] << 8) + data[flen+1];
    flen += 2;
    tmp += QString("%1:%2 | ").arg(u8"标2电压差[12]:").arg(tmp16);

    tmp16 = (data[flen] << 8) + data[flen+1];
    flen += 2;
    tmp += QString("%1:%2").arg(u8"当前电压[13]:").arg(tmp16);
    ui->logTextEdit->appendPlainText(tmp);

    tmp16 = (data[flen] << 8) + data[flen+1];
    flen += 2;
    tmp = QString("%1:%2 | ").arg(u8"传感寿命[14]:").arg(tmp16);

    tmp16 = (data[flen] << 8) + data[flen+1];
    flen += 2;
    tmp += QString("%1:%2 | ").arg(u8"运行时间[15]:").arg(tmp16);

    tmp16 = (data[flen] << 8) + data[flen+1];
    flen += 2;
    tmp += QString("%1:%2 | ").arg(u8"浓度运行时间[16]:").arg(tmp16);

    tmp16 = data[flen++];
    tmp += QString("%1:%2 | ").arg(u8"高温时间[17]:").arg(tmp16);
    tmp16 = data[flen++];
    tmp += QString("%1:%2").arg(u8"低温时间[18]:").arg(tmp16);

    ui->logTextEdit->appendPlainText(tmp);
    ui->logTextEdit->appendPlainText(" ");
}

static const char* GetDeviceName(quint16 type)
{
    static const char* device[7] = {"2303a","2302a","2301a","2305","2392a","2392b","2393"};

    if(type <= 6) return device[type];

    return "unkown device";
}

void MainWindow::parseCapacityFrame(char *frameData)
{
    quint8 *data = reinterpret_cast<quint8 *>(frameData);

    quint16 tmp16;
    tmp16 = (data[0] << 8) + data[1];

    ui->capcityEdit->setText(QString::number(tmp16));
}

void MainWindow::parseCtrlAttrFrame(char *frameData)
{
    ui->logTextEdit->appendPlainText(u8"控制器属性数据解包:");

    quint8 *data = reinterpret_cast<quint8 *>(frameData);

    QString tmp;
    quint8 param0, param1;
    quint16 tmp16, flen = 0;

    tmp16 = (data[flen] << 8) + data[flen+1];
    flen += 2;
    tmp = QString("%1:%2 | ").arg(u8"协议版本[0]:").arg(tmp16);

    param0 = data[flen++]; param1 = data[flen++];
    tmp += QString("%1:V%2.%3.%4 | ").arg(u8"软件版本[1]:")
                                    .arg(param0 / 100)
                                    .arg(param0 % 100, 2, 10, QChar('0'))
                                    .arg(param1, 3, 10, QChar('0'));

    param0 = data[flen++];
    tmp += QString("%1:%2-").arg(u8"时间[2-4]:").arg(param0 + 2000);
    param0 = data[flen++];
    tmp += QString("%1-").arg(param0, 2, 10, QChar('0'));
    param0 = data[flen++];
    tmp += QString("%1 ").arg(param0, 2, 10, QChar('0'));

    param0 = data[flen++];
    tmp += QString("%1:").arg(param0, 2, 10, QChar('0'));
    param0 = data[flen++];
    tmp += QString("%1:").arg(param0, 2, 10, QChar('0'));
    param0 = data[flen++];
    tmp += QString("%1 | ").arg(param0, 2, 10, QChar('0'));

    tmp16 = (data[flen] << 8) + data[flen+1];
    flen += 2;
    tmp += QString("%1:%2 | ").arg(u8"探头总数[5]:").arg(tmp16);

    tmp16 = (data[flen] << 8) + data[flen+1];
    flen += 2;
    tmp += QString("%1:%2 | ").arg(u8"模块总数[6]:").arg(tmp16);

    param0 = data[flen++];
    tmp += QString("%1:%2 ").arg(u8"设备类型[7H]:").arg(GetDeviceName(param0));
    param0 = data[flen++];
    tmp += QString("%1:%2").arg(u8"属性可读标志[7L]:").arg(param0);
    ui->logTextEdit->appendPlainText(tmp);

    tmp = u8"属性变动探头编号[8-23]:";
    int i = 0, j = 1, m = 8;
    bool isChange = false;
    while(1)
    {
        quint8 mask = 1 << (m - 1);
        if(data[flen + i] & mask) {
            tmp += QString("%1 ").arg(j);
            isChange = true;
        }

        j++;
        if(m > 0) m--;
        if(m == 0) {
            m = 8;
            i++;
        }
        if(i >= 32 || j > 256) break;
    }
    if(!isChange) tmp += u8"没有";
    ui->logTextEdit->appendPlainText(tmp);
    ui->logTextEdit->appendPlainText(" ");
}

void MainWindow::SlotTimer()
{
    static quint32 tick = 0;

    if(serialOverTick >= 10) {
        serialOverTick -= 10;
    }
    else if(serialOverTick < 10) {
        if(!recvArray.isEmpty()) {
            int dlen = recvArray.length();
            char *data = recvArray.data();
            writeLog("RCV", data, dlen);
            if(GetCrc16((quint8 *)data, dlen) == 0) {
                if(isTiming) {
                    recvNum++;
                    ui->recvNumEdit->setText(QString::number(recvNum));
                }
                else {
                    if(data[1] == 0x03 && parseAttr > 0) {
                        if((parseAttr & 0x01) && (dlen >= SENSORATTRLEN * 2 + 3))
                            parseSensorAttrFrame(data + 3);
                        else if((parseAttr & 0x02) && (dlen >= CTRLATTRLEN * 2 + 3))
                            parseCtrlAttrFrame(data + 3);
                        else if(parseAttr & 0x04)
                            parseCapacityFrame(data + 3);
                        parseAttr = 0;
                    }
                }
            }
            recvArray.clear();
        }
    }

    if(!isTiming) return;

    if(isOpen) {
        tick += 10;
        if(tick >= overTick) {
            tick = 0;
            if(sendNum > 0) {
                double ratio = sendNum;

                ratio -= recvNum;
                ratio /= sendNum;
                ratio *= 100.00;
                ui->frameRatioEdit->setText(QString::number(ratio, 10, 2));
            }

            if(recvNum < sendNum) {
                quint32 div = sendNum - recvNum;
                if(div != conNum) {
                    conNum = div;
                    continuousNum++;
                    ui->frameRatioEdit_2->setText(QString::number(continuousNum));
                    //QString tmp = QString("continuous:%1 ERR").arg(div);
                    //writeWndLog(tmp);
                }
            }

            modbusSndReadPtl();
            sendNum++;
            ui->sendNumEdit->setText(QString::number(sendNum));
        }
    }
}

void MainWindow::SlotRead()
{
    QByteArray readByte = serialPort->readAll();

    if(readByte.isEmpty()) return;

    recvArray += readByte;
    serialOverTick = 50;
}

void MainWindow::SlotPortError(QSerialPort::SerialPortError error)
{
    if(error != QSerialPort::NoError) {
        QMessageBox::information(0, "tips", u8"串口提示错误");
        if(isOpen) {
            on_serialBtn_clicked();
        }
    }
}

void MainWindow::closePort()
{
    if(serialPort->isOpen()) {
        serialPort->close();
    }
    isOpen = false;

    if(serialPortInfo) {
        delete serialPortInfo;
        serialPortInfo = nullptr;
    }
}

void MainWindow::refreshSerialPort()
{
    ui->serialPortBox->clear();
    for(auto &dev:QSerialPortInfo::availablePorts())
    {
        QSerialPort com;

        com.setPort(dev);
        if(com.open(QIODevice::ReadWrite)) {
            ui->serialPortBox->addItem(com.portName());
            com.close();
        }
    }
}

void MainWindow::on_refreshSerialBtn_clicked()
{
    refreshSerialPort();
}

QString MainWindow::byteArrayToHexStr(const QByteArray &data)
{
    QString temp = "";
    QString hex = data.toHex();

    for (int i = 0; i < hex.length(); i = i + 2) {
        temp += hex.mid(i, 2) + " ";
    }

    return temp.trimmed().toUpper();
}

void MainWindow::writeLog(const char *title, char *buf, int len)
{
    QByteArray tmp = QByteArray((char *)buf, len);
    QString tmp1 = byteArrayToHexStr(tmp);
    QString tmp2 = QString("%1:%2").arg(title).arg(tmp1);

    writeWndLog(tmp2);
}

void MainWindow::writeWndLog(const QString& data)
{
    QString msg = QString("[%1]%2").arg(MyDATETIMEMS).arg(data);
    ui->logTextEdit->appendPlainText(msg);

    if(ui->logTextEdit->blockCount() > 40000) {
        on_fileSaveBtn_clicked();
    }
}

void MainWindow::on_fileSaveBtn_clicked()
{
    QString filename = QString("%1/%2ModbusLog.txt").arg(qApp->applicationDirPath()).arg(STRDATETIME);
    QString data = ui->logTextEdit->toPlainText();

    if(data.isEmpty()) return;

    QFile f(filename);
    if(f.open(QFile::WriteOnly | QFile::Text)) {
        f.write(data.toUtf8());
        f.close();
    }

    //ui->logTextEdit->clear();
}


void MainWindow::on_enbleTestBox_clicked(bool checked)
{
    if(!isOpen) {
        return;
    }

    if(checked) {
        isTiming = true;
        ui->modbusUsrSndBtn->setEnabled(false);
        ui->modbusSndBtn->setEnabled(false);
        ui->serialBtn->setEnabled(false);
        ui->refreshSerialBtn->setEnabled(false);

        ui->overTimeEdit->setEnabled(false);

        overTick = ui->overTimeEdit->text().toUInt();
        if(overTick < 500) {
            overTick = 500;
            ui->overTimeEdit->setText("500");
        }        
    }
    else {
        isTiming = false;
        ui->modbusUsrSndBtn->setEnabled(true);
        ui->modbusSndBtn->setEnabled(true);
        ui->serialBtn->setEnabled(true);
        ui->refreshSerialBtn->setEnabled(true);
        ui->overTimeEdit->setEnabled(true);
    }
}

void MainWindow::on_modbusSndBtn_clicked()
{
    if(!isOpen || isTiming) return;

    modbusSndReadPtl();
}

void MainWindow::modbusSndReadPtl()
{
    uint16_t ptladdr = ui->modbusPtlAddrEdit->text().toUShort();
    uint16_t ptllen  = ui->modbusPtlLenEdit->text().toUShort();

    int cmd = ui->modbusCmdBox->currentIndex() + 1;

    if(ptllen == 0) {
        ui->modbusPtlLenEdit->setText("1");
        ptllen = 1;
    }

    modbusSndReadPtlAttr(cmd, ptladdr, ptllen);
}

void MainWindow::modbusSndReadPtlAttr(quint16 cmd, quint16 ptladdr, quint16 ptllen)
{
    unsigned char buf[8] = {0};

    quint16 addr = ui->clientAddrBox->currentIndex() + 1;

    buf[0] = addr;
    buf[1] = cmd;
    buf[2] = ptladdr >> 8;
    buf[3] = ptladdr;
    buf[4] = ptllen >> 8;
    buf[5] = ptllen;

    if(cmd == 0x03 && ptladdr >= 1000) {
        ptladdr -= 1000;
        if((ptladdr % SENSORATTRLEN == 0) && (ptllen == SENSORATTRLEN)) {
            parseAttr = 0x01;
        }
    }
    else if(cmd == 0x03 && ptladdr == 0 && ptllen == CTRLATTRLEN) {
        parseAttr = 0x02;
    }
    else if(cmd == 0x03 && ptladdr == 0x3F) {
        parseAttr = 0x04;
    }

    uint16_t crcVal = GetCrc16(buf, 6);
    buf[6] = crcVal >> 8;
    buf[7] = crcVal;

    writeLog("SND", (char *)buf, 8);
    serialPort->write((char *)buf, 8);
}

int MainWindow::hexStrToChar(char ch)
{
    if(ch >= '0' && ch <= '9') {
        return ch - '0';
    }

    if(ch >= 'a' && ch <= 'f') {
        return ch - 'a' + 10;
    }

    if(ch >= 'A' && ch <= 'F') {
        return ch - 'A' + 10;
    }

    return 0xff;
}

QByteArray MainWindow::hexStrToByteArray(const QString& data)
{
    QByteArray tmp;

    int len = data.length(), i, j;
    tmp.resize(len/2);

    int hhex, lhex;
    for(i = 0, j = 0; i < len;) {
        char hch = data.at(i).toLatin1();
        if(hch == ' ' ) {
            i++;
            continue;
        }
        i++;
        if(i >= len) break;
        hhex = hexStrToChar(hch);

        char lch = data.at(i).toLatin1();
        lhex = hexStrToChar(lch);

        i++;

        //左字符出错 || 右字符出错且不是空格跳过
        if(hhex == 0xff) continue;
        if(lhex == 0xff && lch != ' ') continue;
        //右字符是空格，则左字符按单字符补0处理
        if(lhex != 0xff) {
            hhex = hhex * 16 + lhex;
        }
        tmp[j] = static_cast<char>(hhex);
        j++;
    }

    if(j < len/2) {
        tmp.remove(j, len/2 - j);
    }

    return tmp;
}

void MainWindow::on_modbusUsrSndBtn_clicked()
{
    if(!isOpen || isTiming) return;

    QString input = ui->modbusSetTtextEdit->toPlainText();
    input.replace(" ", "");

    int len = input.length();
    if(len > 0) {
        int i = 0, j = 0;
        for(i = 0; i < len; i++)
        {
            if((input.at(i) >= '0' && input.at(i) <= '9')
            || (input.at(i) >= 'A' && input.at(i) <= 'F')
            || (input.at(i) >= 'a' && input.at(i) <= 'f')) {
                j++;
            }
        }
        if(i != j) {
            QMessageBox::information(nullptr, "warning", "invalid hex string!");
        }
        else {
            QByteArray data2 = hexStrToByteArray(input);
            quint16 crcval = GetCrc16((quint8 *)data2.data(), (quint16)data2.length());
            data2.push_back(static_cast<char>(crcval >> 8));
            data2.push_back(static_cast<char>(crcval&0xff));

            writeLog("SND", data2.data(), data2.length());

            serialPort->write(data2);
        }
    }
}

void MainWindow::refreshTimeBtn_clicked()
{
    if(!isOpen || isTiming) return;

    unsigned char buf[16] = {0};

    uint16_t addr    = ui->clientAddrBox->currentIndex() + 1;
    buf[0] = addr;
    buf[1] = 0x10;
    buf[2] = 0;
    buf[3] = 0x02;
    buf[4] = 0;
    buf[5] = 0x03;
    buf[6] = 0x06;

    QDateTime t = QDateTime::currentDateTime();

    quint16 tmp16;
    tmp16 = t.date().year() % 100;
    buf[7] = tmp16;
    buf[8] = t.date().month();
    buf[9] = t.date().day();
    buf[10] = t.time().hour();
    buf[11] = t.time().minute();
    buf[12] = t.time().second();

    tmp16 = GetCrc16(buf, 13);
    buf[13] = tmp16 >> 8;
    buf[14] = tmp16;

    writeLog("SND", reinterpret_cast<char *>(buf), 15);
    serialPort->write(reinterpret_cast<char *>(buf), 15);
}

void MainWindow::readSensorAttrBtn_clicked()
{
    if(!isOpen || isTiming) return;

    int index = ui->sensorAddrBox->currentIndex();

    index *= 25;
    index += 1000;
    ui->modbusPtlAddrEdit->setText(QString::number(index));
    ui->modbusPtlLenEdit->setText("25");

    modbusSndReadPtlAttr(3, index, 25);
}

void MainWindow::readCtrlConfigBtn_clicked()
{
    if(!isOpen || isTiming) return;

    ui->modbusPtlAddrEdit->setText("0");
    ui->modbusPtlLenEdit->setText("24");

    modbusSndReadPtlAttr(3, 0, 24);
}

void MainWindow::readCapcityBtn_clicked()
{
    if(!isOpen || isTiming) return;

    modbusSndReadPtlAttr(3, 0x3F, 1);
}

void MainWindow::setCapcityBtn_clicked()
{
    if(!isOpen || isTiming) return;

    uint16_t capacity = ui->capcityEdit->text().toUShort();
    if(capacity < 1 || capacity > 1024) {
        QMessageBox::information(0, "tips", u8"容量正确范围[1, 1024]");
        return;
    }

    uint16_t addr    = ui->clientAddrBox->currentIndex() + 1, tmp16;
    unsigned char buf[12] = {0};

    buf[0] = addr;
    buf[1] = 0x10;
    buf[2] = 0;
    buf[3] = 0x3F;
    buf[4] = 0;
    buf[5] = 0x01;
    buf[6] = 0x02;
    buf[7] = capacity >> 8;
    buf[8] = capacity;
    tmp16 = GetCrc16(buf, 9);
    buf[9] = tmp16 >> 8;
    buf[10] = tmp16;

    writeLog("SND", reinterpret_cast<char *>(buf), 11);
    serialPort->write(reinterpret_cast<char *>(buf), 11);
}

void MainWindow::on_oprationBtn_clicked()
{
    if(!isOpen || isTiming) return;
    /*
    ui->opCmdBox->addItem(u8"1. 读取控制器属性");
    ui->opCmdBox->addItem(u8"2. 读取指定探头全属性");
    ui->opCmdBox->addItem(u8"3. 同步设备时间");
    ui->opCmdBox->addItem(u8"4. 读取设备容量");
    ui->opCmdBox->addItem(u8"5. 设置设备容量");
    ui->opCmdBox->addItem(u8"6. 复位设备");
    ui->opCmdBox->addItem(u8"7. 设备消声");
    */
    switch(ui->opCmdBox->currentIndex())
    {
    case 0:
        readCtrlConfigBtn_clicked();
        break;
    case 1:
        readSensorAttrBtn_clicked();
        break;
    case 2:
        refreshTimeBtn_clicked();
        break;
    case 3:
        readCapcityBtn_clicked();
        break;
    case 4:
        setCapcityBtn_clicked();
        break;
    case 5:
        modbusSndReadPtlAttr(5, 126, 0);
        break;
    case 6:
        modbusSndReadPtlAttr(5, 127, 0);
        break;
    }
}
